Skip to content

fix(callkit): add voip background mode so incoming calls work#84

Merged
torlando-tech merged 2 commits into
mainfrom
fix/voip-callkit-background-mode
Jun 1, 2026
Merged

fix(callkit): add voip background mode so incoming calls work#84
torlando-tech merged 2 commits into
mainfrom
fix/voip-callkit-background-mode

Conversation

@torlando-tech

@torlando-tech torlando-tech commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Incoming voice calls were unusable on device: the CallKit screen briefly showed the caller's name, auto-dismissed, reappeared as "Unknown", and couldn't be answered (or had no audio).

Root cause

The app's UIBackgroundModes was missing voip, so callservicesd denied creation of the CallKit call source and reset the provider:

callservicesd  Denying creation of CXXPCCallSource … Not accepting connection (ColumbaApp)
→ [CALLKIT] providerDidReset → CallManager.handleCallKitReset() → currentCallUUID=nil + callState=.ended

That teardown is the auto-dismiss; the wiped peerHash is why the re-identify shows "Unknown"; the churn is why you can't answer. The device diag.log only showed the effect (providerDidReset) — the reason (the callservicesd deny) was only visible in the simulator's com.apple.calls.callkit unified log.

Fix

  • Info.plist: add voip (+ audio, standard for a calling app) to UIBackgroundModes; add NSMicrophoneUsageDescription (the "no audio" half — mic capture needs it). voip is just an Info.plist key — no provisioning/entitlement capability required.
  • Tests/interop/voice_caller.py: a headless LXST caller (Sideband's ReticulumTelephoneLXST.Telephone, wire-identical to a Sideband user calling) that joins the shared lxmd instance and dials the sim's telephony dest, reproducing the incoming-call/CallKit path on the simulator — where CallKit runs, so the framework deny reason is observable.

Verified

  • Simulator: with the fix, reportIncomingCall succeeds and callservicesd logs "Created CXXPCCallSource … Asked to add call source" — no deny, no providerDidReset, no teardown.
  • On device (iPhone): incoming call connects + answers, Opus audio confirmed working both ways. ⚠️ Codec2 profile not yet tested — follow-up.

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes incoming CallKit calls by adding the missing voip UIBackgroundMode (which caused callservicesd to deny the call source and trigger providerDidReset) and NSMicrophoneUsageDescription (required for mic capture). It also ships a headless Python test caller that reproduces the incoming-call path on the simulator where the callservicesd deny log is observable.

  • Info.plist: adds audio and voip to UIBackgroundModes and NSMicrophoneUsageDescription; all additions are in correct alphabetical order and follow Apple's standard pattern for VoIP/audio apps.
  • Tests/interop/voice_caller.py: new debug tool that joins the host's shared lxmd RNS instance and dials Columba's telephony destination via Sideband's ReticulumTelephone, wire-identical to a real Sideband peer. Checkout paths are resolved via LXST_SRC/SIDEBAND_SRC env vars with informative error messages on failure.

Confidence Score: 5/5

Safe to merge — the Info.plist additions are minimal and correct, and the new test script is a dev-only debug tool with no production impact.

Both changes are narrow and well-understood. The voip + audio background modes and NSMicrophoneUsageDescription are exactly what Apple requires for a CallKit calling app — no entitlement changes needed, no logic changes, no migration risk. The Python test script is isolated to Tests/interop/, introduces no new runtime dependencies in the app, and already handles its external-path concerns via env-var overrides with clear error messages on failure.

No files require special attention.

Important Files Changed

Filename Overview
Sources/ColumbaApp/Resources/Info.plist Adds voip + audio UIBackgroundModes and NSMicrophoneUsageDescription — all three are standard for a CallKit calling app and are correctly positioned in alphabetical order within their respective arrays/dict.
Tests/interop/voice_caller.py New headless LXST/Sideband caller for reproducing incoming-call/CallKit paths on the simulator; env-var overrides for checkout paths, clear path-resolution loop, and guarded hangup. No unused imports; no logic errors found.

Sequence Diagram

sequenceDiagram
    participant Caller as voice_caller.py (LXST/Sideband)
    participant lxmd as lxmd (shared RNS)
    participant Columba as ColumbaApp (iOS)
    participant CK as CallKit / callservicesd

    Note over Caller: RNS.Reticulum() — joins shared lxmd
    Caller->>lxmd: phone.announce()
    Caller->>lxmd: Transport.request_path(tel_dest)
    lxmd-->>Caller: path resolved
    Caller->>Columba: phone.dial(id_bytes) via LXST.Telephone

    Columba->>CK: reportIncomingCall(uuid, update)
    Note over CK: BEFORE fix: callservicesd denies CXXPCCallSource<br/>(missing voip UIBackgroundMode)<br/>→ providerDidReset → currentCallUUID=nil → dismiss

    Note over CK: AFTER fix: voip + audio in UIBackgroundModes<br/>+ NSMicrophoneUsageDescription present
    CK-->>Columba: provider(_:perform: CXAnswerCallAction)
    Columba-->>Caller: call connected, Opus audio active
Loading

Reviews (4): Last reviewed commit: "address greptile review feedback (greplo..." | Re-trigger Greptile

Comment thread Tests/interop/voice_caller.py Outdated
Comment thread Tests/interop/voice_caller.py Outdated
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile review

torlando-agent Bot and others added 2 commits May 31, 2026 21:42
…alls work

Incoming voice calls were unusable: the CallKit screen flashed the caller's
name, auto-dismissed, reappeared as 'Unknown', and couldn't be answered.

Root cause: the app's UIBackgroundModes was missing 'voip', so callservicesd
DENIED creation of the CallKit call source and reset the provider
(providerDidReset → CallManager.handleCallKitReset → currentCallUUID=nil +
callState=.ended). Found via the simulator's com.apple.calls.callkit unified
log ('Denying creation of CXXPCCallSource … Not accepting connection'); the
device diag.log only showed the effect (providerDidReset).

- Info.plist: add 'voip' (+ 'audio') to UIBackgroundModes; add
  NSMicrophoneUsageDescription (the 'no audio' half — mic capture needs it).
  'voip' is just an Info.plist key, no provisioning capability required.
- Tests/interop/voice_caller.py: headless LXST caller (Sideband's
  ReticulumTelephone, wire-identical) that dials the sim's telephony dest to
  reproduce the incoming-call/CallKit path on the simulator — where CallKit
  runs, so the bug + the framework deny reason are observable (unlike on device).

Verified on sim: with the fix, reportIncomingCall succeeds + callservicesd
'Created CXXPCCallSource' — no deny, no providerDidReset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- voice_caller.py: drop unused 'threading' import; make LXST/Sideband
  checkout paths overridable via LXST_SRC/SIDEBAND_SRC (SIDEBAND_SRC matches
  conftest) with a clear error if missing, instead of hardcoded ~/repos paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@torlando-tech torlando-tech force-pushed the fix/voip-callkit-background-mode branch from 53b1d26 to a89228a Compare June 1, 2026 01:42
@torlando-tech torlando-tech merged commit 49fa4bb into main Jun 1, 2026
2 checks passed
@torlando-tech torlando-tech deleted the fix/voip-callkit-background-mode branch June 1, 2026 01:50
@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant